---
title: Drupal & Astro
description: Drupal을 CMS로 사용하여 Astro 프로젝트에 콘텐츠 추가하기
sidebar:
  label: Drupal
type: cms
logo: drupal
i18nReady: true
stub: true
---
import { FileTree, Steps, CardGrid, LinkCard } from '@astrojs/starlight/components';
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro';
import ReadMore from '~/components/ReadMore.astro';

[Drupal](https://www.drupal.org/)은 오픈소스 콘텐츠 관리 도구입니다.

## 전제 조건

시작하려면 다음이 필요합니다:

1. **Astro 프로젝트** - 아직 Astro 프로젝트가 없는 경우, [설치 가이드](/ko/install-and-setup/)를 참조하면 금방 시작할 수 있습니다.

2. **Drupal 사이트** - Drupal 사이트를 설정하지 않은 경우 공식 가이드라인 [Drupal 설치하기](https://www.drupal.org/docs/getting-started/installing-drupal)를 따르세요.

## Drupal과 Astro 통합

### JSON:API Drupal 모듈 설치하기

Drupal에서 콘텐츠를 가져오려면 [Drupal JSON:API 모듈](https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module)을 활성화해야 합니다.

1. Manage administrative 메뉴를 통해 Extend 페이지 `admin/modules`로 이동합니다.
2. JSON:API 모듈을 찾아 옆의 확인란을 선택합니다.
3. Install을 클릭하여 새 모듈을 설치합니다.

이제 JSON:API를 통해 Drupal 애플리케이션에 `GET` 요청을 할 수 있습니다.

### `.env`에 Drupal URL 추가하기

Astro에 Drupal URL을 추가하려면 프로젝트의 루트에 `.env` 파일을 만들고 (아직 없는 경우) 다음 변수를 추가하세요:

```ini title=".env"
DRUPAL_BASE_URL="https://drupal.ddev.site/"
```

개발 서버를 재시작하여 Astro 프로젝트에서 이 환경 변수를 사용하세요.

### 자격 증명 설정

기본적으로 외부 데이터 가져오기 요청에 대해 인증 없이도 Drupal JSON:API 엔드포인트에 액세스할 수 있습니다. 이를 통해 자격 증명 없이도 Astro 프로젝트에서 데이터를 가져올 수 있지만 사용자가 데이터나 사이트 설정을 수정할 수는 없습니다.

그러나 액세스를 제한하고 인증이 필요한 경우 Drupal은 다음과 같은 [여러 인증 방법](https://www.drupal.org/docs/contributed-modules/api-authentication)을 제공합니다:

- [기본 인증](https://www.drupal.org/docs/contributed-modules/api-authentication/setup-basic-authentication)
- [API Key 기반 인증](https://www.drupal.org/docs/contributed-modules/api-authentication/api-key-authentication)
- [액세스 토큰/OAuth 기반 인증](https://www.drupal.org/docs/contributed-modules/api-authentication/setup-access-token-oauth-based-authentication)
- [JWT Token 기반 인증](https://www.drupal.org/docs/contributed-modules/api-authentication/jwt-authentication)
- [타사 공급자 토큰 인증](https://www.drupal.org/docs/contributed-modules/api-authentication/rest-api-authentication-using-external-identity-provider)

`.env` 파일에 자격 증명을 추가할 수 있습니다.

```ini title=".env"
DRUPAL_BASIC_USERNAME="editor"
DRUPAL_BASIC_PASSWORD="editor"
DRUPAL_JWT_TOKEN="abc123"
...
```

<ReadMore>Astro의 [환경 변수 사용](/ko/guides/environment-variables/) 및 `.env` 파일에 대해 자세히 알아보세요.</ReadMore>

이제 루트 디렉터리에 이 새 파일들이 포함되어야 합니다:

<FileTree title="프로젝트 구조">
- **.env**
- astro.config.mjs
- package.json
</FileTree>

### 종속성 설치

JSON:API 요청과 응답은 종종 복잡하고 깊게 중첩될 수 있습니다. 작업을 간소화하기 위해 요청과 응답 처리를 모두 간소화하는 두 가지 npm 패키지를 사용할 수 있습니다:
- [`JSONA`](https://www.npmjs.com/package/jsona): 서버와 브라우저에서 사용하기 위해 JSON API v1.0 사양을 직렬화 및 역직렬화 할 수 있습니다.
- [`Drupal JSON-API Params`](https://www.npmjs.com/package/drupal-jsonapi-params): 이 모듈은 필요한 쿼리를 생성하는 헬퍼 클래스를 제공합니다. 이 과정에서 가능한 경우 짧은 형식을 사용하여 쿼리를 최적화하려고 시도합니다.

<PackageManagerTabs>
  <Fragment slot="npm">
  ```shell
  npm install jsona drupal-jsonapi-params
  ```
  </Fragment>
  <Fragment slot="pnpm">
  ```shell
  pnpm add jsona drupal-jsonapi-params
  ```
  </Fragment>
  <Fragment slot="yarn">
  ```shell
  yarn add jsona drupal-jsonapi-params
  ```
  </Fragment>
</PackageManagerTabs>

## Drupal에서 데이터 가져오기

콘텐츠는 JSON:API URL에서 가져옵니다.

### Drupal JSON:API URL 구조

기본 URL 구조: `/jsonapi/{entity_type_id}/{bundle_id}`

URL 앞에는 항상 `jsonapi`가 붙습니다.
- `entity_type_id`는 node, block, user 등의 엔티티 타입을 나타냅니다.
- `bundle_id`는 엔티티 번들을 나타냅니다. Node 엔티티 타입의 경우 번들은 article이 될 수 있습니다.
- 이 경우 모든 articles 목록을 가져오기 위해 URL은 `[DRUPAL_BASE_URL]/jsonapi/node/article`이 됩니다.

개별 엔티티를 검색하기 위한 URL 구조는 `/jsonapi/{entity_type_id}/{bundle_id}/{uuid}`이며, 여기서 uuid는 엔티티의 UUID입니다. 예를 들어 특정 article을 가져오는 URL은 `/jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e` 형태가 됩니다.

#### 특정 필드만 검색

쿼리 문자열 필드를 요청에 추가하여 특정 필드만 검색합니다.

GET: `/jsonapi/{entity_type_id}/{bundle_id}?field[entity_type]=field_list`

예시:
- `/jsonapi/node/article?fields[node--article]=title,created`
- `/jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e?fields[node--article]=title,created,body`

#### 필터링

쿼리 문자열 필터를 추가하여 요청에 필터를 추가합니다.

가장 간단하고 일반적인 필터는 키-값 필터입니다:

GET: `/jsonapi/{entity_type_id}/{bundle_id}?filter[field_name]=value&filter[field_other]=value`

예시:
- `/jsonapi/node/article?filter[title]=Testing JSON:API&filter[status]=1`
- `/jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e?fields[node--article]=title&filter[title]=Testing JSON:API`

더 많은 쿼리 옵션은 [JSON:API 문서](https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module)에서 확인할 수 있습니다.

### Drupal 쿼리 작성하기

Astro 컴포넌트는 `drupal-jsonapi-params` 패키지로 쿼리를 작성하여 Drupal 사이트에서 데이터를 가져올 수 있습니다.

다음 예시는 "article" 콘텐츠 타입을 쿼리하는 컴포넌트를 보여줍니다. 이 콘텐츠 타입은 제목용 일반 텍스트 필드와 본문용 서식 있는 텍스트 필드를 포함하고 있습니다.

```astro
---
import {Jsona} from "jsona";
import {DrupalJsonApiParams} from "drupal-jsonapi-params";
import type {TJsonApiBody} from "jsona/lib/JsonaTypes";

// Drupal 기본 URL 가져오기
export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;

// JSON:API 쿼리를 생성합니다. 게시된 articles에서 모든 제목과 본문을 가져옵니다.
const params: DrupalJsonApiParams = new DrupalJsonApiParams();
params.addFields("node--article", [
        "title",
        "body",
    ])
    .addFilter("status", "1");
// 쿼리 문자열 생성
const path: string = params.getQueryString();
const url: string = baseUrl + '/jsonapi/node/article?' + path;

// articles 가져오기
const request: Response = await fetch(url);
const json: string | TJsonApiBody = await request.json();
// Jsona 초기화
const dataFormatter: Jsona = new Jsona();
// 응답 역직렬화
const articles = dataFormatter.deserialize(json);
---
<body>
 {articles?.length ? articles.map((article: any) => (
    <section>
      <h2>{article.title}</h2>
      <article set:html={article.body.value}></article>
    </section>
 )): <div><h1>No Content found</h1></div> }
</body>
```

더 많은 쿼리 옵션은 [Drupal JSON:API 문서](https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module/jsonapi)에서 확인할 수 있습니다.

## Astro 및 Drupal로 블로그 만들기

위의 설정으로 이제 Drupal을 CMS로 사용하는 블로그를 만들 수 있습니다.

### 전제 조건

1. [`JSONA`](https://www.npmjs.com/package/jsona) 및 [`Drupal JSON-API Params`](https://www.npmjs.com/package/drupal-jsonapi-params)가 설치된 **Astro 프로젝트**

2. **항목이 하나 이상 있는 Drupal 사이트** - 이 튜토리얼에서는 표준 설치로 새 Drupal 사이트를 시작하는 것을 권장합니다.

    Drupal 사이트의 **Content** 섹션에서 **Add** 버튼을 클릭하여 새 항목을 만듭니다. 그런 다음 Article을 선택하고 필드를 채웁니다:

    - **Title:** `My first article for Astro!`
    - **Alias:** `/articles/first-article-for astro`
    - **Description:** `This is my first Astro article! Let's see what it will look like!`

    **Save**를 클릭하여 첫 번째 Article을 작성합니다. 원하는 만큼 자유롭게 articles를 추가할 수 있습니다.

### articles 목록 표시

<Steps>

1. 아직 존재하지 않는 경우 `src/types.ts`를 생성하고 다음 코드를 사용하여 `DrupalNode` 및 `Path`라는 두 개의 새 인터페이스를 추가합니다. 이 인터페이스는 Drupal의 article 콘텐츠 타입 필드 및 Path 필드와 일치합니다. 이를 사용하여 article 항목 응답에 대한 타입을 정의합니다.

    ```ts title="src/types.ts"

    export interface Path {
        alias: string
        pid: number
        langcode: string
    }

    export interface DrupalNode extends Record<string, any> {
        id: string
        type: string
        langcode: string
        status: boolean
        drupal_internal__nid: number
        drupal_internal__vid: number
        changed: string
        created: string
        title: string
        default_langcode: boolean
        sticky: boolean
        path: Path
    }
    ```

    이제 src 디렉터리에 새 파일이 포함되어야 합니다:

    <FileTree title="프로젝트 구조">
    - .env
    - astro.config.mjs
    - package.json
    - src/
      - **types.ts**
    </FileTree>

2. `src/api`에 `drupal.ts`라는 새 파일을 만들고 다음 코드를 추가합니다:

    ```ts title="src/api/drupal.ts"
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {DrupalNode} from "../types.ts";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";

    // Drupal 기본 Url을 가져옵니다.
    export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;
    ```
    
    이렇게 하면 응답을 역직렬화하기 위한 `Jsona`, 요청 URL 및 Node와 Jsona 타입의 형식을 지정하는 `DrupalJsonApiParams`와 같은 필수 라이브러리를 가져옵니다.
    또한 `.env` 파일에서 `baseUrl`을 가져옵니다.

    이제 src/api 디렉터리에 새 파일이 포함되어야 합니다:

    <FileTree title="프로젝트 구조">
    - .env
    - astro.config.mjs
    - package.json
    - src/
      - api/
        - **drupal.ts**
      - types.ts
    </FileTree>

3. 같은 파일에서 `fetchUrl` 함수를 생성하여 가져오기 요청을 하고 응답을 역직렬화합니다.

    ```ts title="src/api/drupal.ts" ins={9-23}
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {DrupalNode} from "../types.ts";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";

    // Drupal 기본 Url 가져오기
    export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;

    /**
     * Fetch url from Drupal.
     *
     * @param url
     *
     * @return Promise<TJsonaModel | TJsonaModel[]> as Promise<any>
     */
    export const fetchUrl = async (url: string): Promise<any> => {
        const request: Response = await fetch(url);
        const json: string | TJsonApiBody = await request.json();
        const dataFormatter: Jsona = new Jsona();
        return dataFormatter.deserialize(json);
    }
    ```

4. 게시된 모든 articles를 가져오는 `getArticles()` 함수를 만듭니다.

    ```ts title="src/api/drupal.ts" ins={23-40}
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {DrupalNode} from "../types.ts";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";

    // Drupal 기본 Url 가져오기
    export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;

    /**
     * Fetch url from Drupal.
     *
     * @param url
     *
     * @return Promise<TJsonaModel | TJsonaModel[]> as Promise<any>
     */
    export const fetchUrl = async (url: string): Promise<any> => {
        const request: Response = await fetch(url);
        const json: string | TJsonApiBody = await request.json();
        const dataFormatter: Jsona = new Jsona();
        return dataFormatter.deserialize(json);
    }

    /**
     * Get all published articles.
     *
     * @return Promise<DrupalNode[]>
     */
    export const getArticles = async (): Promise<DrupalNode[]> => {
        const params: DrupalJsonApiParams = new DrupalJsonApiParams();
        params
            .addFields("node--article", [
                "title",
                "path",
                "body",
                "created",
            ])
            .addFilter("status", "1");
        const path: string = params.getQueryString();
        return await fetchUrl(baseUrl + '/jsonapi/node/article?' + path);
    }
    ```

    이제 `.astro` 컴포넌트에서 `getArticles()` 함수를 사용하여 각 제목, 본문, 경로, 작성 날짜에 대한 데이터가 포함된 게시된 모든 articles를 가져올 수 있습니다.

5. Drupal에서 데이터를 가져올 Astro 페이지로 이동합니다. 다음 예시는 `src/pages/articles/index.astro`에 articles 랜딩 페이지를 만듭니다.
    
    필요한 종속성을 가져오세요. 그리고 `getArticles()` 함수를 사용하여 Drupal에서 `article` 콘텐츠 타입의 모든 항목을 가져오세요. 이때 응답의 타입을 지정하기 위해 `DrupalNode` 인터페이스를 함수에 전달하세요.

    ```astro title="src/pages/articles/index.astro"
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";

    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";

    // 게시된 모든 articles 가져오기
    const articles = await getArticles();
    ---
    ```

    getArticles()를 사용한 이 가져오기 호출은 페이지 템플릿에서 사용할 수 있는 항목의 타입이 있는 배열을 반환합니다.

    동일한 페이지 파일을 사용했다면 이제 `src/pages/` 디렉터리에 새 파일이 포함되어야 합니다:
    <FileTree title="프로젝트 구조">
    - .env
    - astro.config.mjs
    - package.json
    - src/
      - api/
        - drupal.ts
      - pages/
        - articles/
          - **index.astro**
      - types.ts
    </FileTree>

6.  제목과 같은 콘텐츠를 페이지에 추가합니다. `articles.map()`을 사용하여 Drupal 항목을 목록의 줄 항목으로 표시합니다.

    ```astro title="src/pages/articles/index.astro" ins={12-29}
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";

    import type { DrupalNode } from "../types";
    import {getArticles} from "../api/drupal";

    // 게시된 모든 articles 가져오기
    const articles = await getArticles();
    ---
    <html lang="en">
      <head>
        <title>My news site</title>
      </head>
      <body>
        <h1>My news site</h1>
        <ul>
          {articles.map((article: DrupalNode) => (
            <li>
              <a href={article.path.alias.replace("internal:en/", "")}>
                <h2>{article.title}</h2>
                <p>Published on {article.created}</p>
              </a>
            </li>
          ))}
        </ul>
      </body>
    </html>
    ```

</Steps>

### 개별 블로그 게시물 생성

위와 동일한 방법을 사용하여 Drupal에서 데이터를 가져오되 이번에는 각 article에 대해 고유한 페이지 경로를 만드는 페이지에서 데이터를 가져옵니다.

이 예시는 Astro의 기본 정적 모드를 사용하고 `getStaticPaths()` 함수를 사용하여 [동적 라우팅 페이지 파일](/ko/guides/routing/#동적-라우트)을 생성합니다. 이 함수는 빌드 시 호출되어 페이지가 되는 경로 목록을 생성합니다.

<Steps>

1. 새 파일 `src/pages/articles/[path].astro`를 만들고 `src/api/drupal.ts`에서 `DrupalNode` 인터페이스와 `getArticle()`을 가져옵니다. `getStaticPaths()` 함수에서 데이터를 가져와 블로그의 경로를 만듭니다.

    ```astro title="src/pages/articles/[path].astro"
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";

    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";

    // 게시된 모든 articles 가져오기
    export async function getStaticPaths() {
      const articles = await getArticles();
    }
    ---
    ```

    이제 src/pages/articles 디렉터리에 새 파일이 포함되어야 합니다:
    <FileTree title="프로젝트 구조">
    - .env
    - astro.config.mjs
    - package.json
    - src/
      - api/
        - drupal.ts
      - pages/
        - articles/
          - index.astro
          - **[path].astro**
      - types.ts
    </FileTree>

2. 같은 파일에서 각 Drupal 항목을 `params` 및 `props` 속성을 가진 객체에 매핑합니다. `params` 속성은 페이지의 URL을 생성하는 데 사용되며 `props` 값은 페이지 컴포넌트에 props로 전달됩니다.

    ```astro title="src/pages/articles/[path].astro" ins={12-33}
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";

    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";

    // 게시된 모든 articles 가져오기
    export async function getStaticPaths() {
        const articles = await getArticles();
        return articles.map((article: DrupalNode) => {
            return {
                params: {
                    // `path`를 `[path]` 라우팅 값과 일치하도록 선택
                    path: article.path.alias.split('/')[2]
                },
                props: {
                    title: article.title,
                    body: article.body,
                    date: new Date(article.created).toLocaleDateString('en-EN', {
                        day: "numeric",
                        month: "long",
                        year: "numeric"
                    })
                }
            }
        });
    }
    ---
    ```

    `params` 내부의 속성은 동적 경로의 이름과 일치해야 합니다. 파일 이름이 `[path].astro`이므로 `params`에 전달되는 속성 이름은 `path`여야 합니다.
    
    예시에서 `props` 객체는 페이지에 세 가지 속성을 전달합니다:
    - `title`: 글의 제목을 나타내는 문자열입니다.
    - `body`: 글의 내용을 나타내는 문자열입니다.
    - `date`: 파일 작성 날짜를 기준으로 한 타임스탬프입니다.

3. 페이지 `props`를 사용하여 블로그 글을 표시합니다.

    ```astro title="src/pages/articles/[path].astro" ins={30, 32-42}
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";

    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";

    // 게시된 모든 articles 가져오기
    export async function getStaticPaths() {
        const articles = await getArticles();
        return articles.map((article: DrupalNode) => {
            return {
                params: {
                    path: article.path.alias.split('/')[2]
                },
                props: {
                    title: article.title,
                    body: article.body,
                    date: new Date(article.created).toLocaleDateString('en-EN', {
                        day: "numeric",
                        month: "long",
                        year: "numeric"
                    })
                }
            }
        });
    }

    const {title, date, body} = Astro.props;
    ---
    <html lang="en">
      <head>
        <title>{title}</title>
      </head>
      <body>
        <h1>{title}</h1>
        <time>{date}</time>
        <article set:html={body.value} />
      </body>
    </html>
    ```

4. 개발 서버 미리 보기로 이동하여 게시물 중 하나를 클릭하여 동적 경로가 작동하는지 확인합니다.

</Steps>

### 사이트 게시

웹사이트를 배포하려면 [배포 가이드](/ko/guides/deploy/)를 방문하여 선호하는 호스팅 제공업체의 지침을 따르세요.

## 커뮤니티 리소스

<CardGrid>

  <LinkCard title="Astro와 Drupal로 웹 애플리케이션 만들기" href="https://www.linkedin.com/pulse/create-web-application-astrojs-website-generator-using-gambino-kx6cf"/>

</CardGrid>

:::note[공유할 리소스가 있나요?]
Astro와 Drupal을 함께 사용하는 방법에 대한 유용한 비디오나 블로그 게시물을 찾았거나 직접 만들었다면, [이 목록](https://github.com/withastro/docs/edit/main/src/content/docs/en/guides/cms/drupal.mdx)에 추가해 주세요!
:::
